package com.yhb.service;

import com.yhb.channel.FlowService;
import com.yhb.channel.dao.flow.ExecutionFlow;
import com.yhb.controller.vo.*;
import com.yhb.dao.ActivateBonusMapper;
import com.yhb.dao.FlowSumUpMapper;
import com.yhb.model.*;
import com.yhb.model.mq.SumUpParam;
import com.yhb.third.leshua.LeShuaFlow;
import com.yhb.third.leshua.dao.LeshuaExecutionFlowMapper;
import com.yhb.utils.DateUtils;
import com.yhb.yinsheng.flow.UnionPayExecutionFlowMapper;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.log4j.Log4j;
import lombok.var;
import me.edzh.spring.bootstrap.model.Tuple;
import me.edzh.spring.bootstrap.service.ObjectiveStorageService;
import me.edzh.spring.bootstrap.utils.JSONUtil;
import me.edzh.spring.bootstrap.utils.MathUtils;
import me.edzh.spring.bootstrap.utils.exception.ServiceException;
import me.edzh.spring.bootstrap.utils.http.ClientHolder;
import okhttp3.Request;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@Service
@Log4j
public class SplittingService {

    @Autowired
    private UserService userService;

    @Autowired
    private FlowService flowService;

    @Autowired
    private FlowSumUpMapper flowSumUpMapper;

    @Autowired
    private UnionPayExecutionFlowMapper unionPayExecutionFlowMapper;

    @Autowired
    private LeshuaExecutionFlowMapper leshuaExecutionFlowMapper;

    @Autowired
    private BalanceService balanceService;

    @Qualifier("ossServiceImpl")
    @Autowired
    private ObjectiveStorageService objectiveStorageService;

    @Lazy
    @Qualifier("MQServiceImpl")
    @Autowired
    private MQService mqService;

    @Autowired
    private MessageService messageService;

    @Autowired
    private CpsService cpsService;

    @Autowired
    private ActivateBonusMapper activateBonusMapper;

    @Autowired
    private RedisTemplate<String, String> redis;

    private final static String SUMMARY_MONTH = "income-summary-report-monthString";

    public final static String TYPE_ONLINE = "splitting_online";
    public final static String TYPE_POS = "splitting_pos";
    public final static String TYPE_UNION = "splitting_union_pay";
    public final static String TYPE_OFFLINE = "splitting_offline";





    public SplittingFlow getSplitting(String splittingType, String monthString, String userId, SplittingFlow.Type incomeType) {
        try {
            var flow = flowSumUpMapper.sumUpSplitting(splittingType, monthString, userId, incomeType);
            if (flow == null) {
                return new SplittingFlow();
            }

            return flow;
        } catch (Throwable t) {
            return new SplittingFlow();
        }
    }

    public SplittingFlow getSplitting(String splittingType, String monthString, String userId, Date startTime, Date endTime) {
        try {
            var flow = flowSumUpMapper.sumUpSplittingFromTimeScale(splittingType, monthString, userId, startTime, endTime);
            if (flow == null) {
                return new SplittingFlow();
            }

            return flow;
        } catch (Throwable t) {
            log.error(t);
            return new SplittingFlow();
        }
    }

    // region 交易量统计
    @Async
    public void sumUpAmount(SumUpParam param) {

        for (String type : Arrays.asList(TYPE_UNION, TYPE_POS, TYPE_ONLINE)) {
            try {
                flowSumUpMapper.removeDateFlows(type, param.getMonthString(), param.getDateString());

            } catch (Throwable t) {
                // 没表, 删不掉正常
            }
        }

        sumUpOnlineAmount(param);
        sumUpUnionPayAmount(param);
        sumUpPosAmount(param);

    }

    public Tuple<BigDecimal, BigDecimal> sumUpOnlineAmount(SumUpParam param) {
        BigDecimal total = BigDecimal.ZERO;
        BigDecimal ordinaryTotal = BigDecimal.ZERO;

        List<String> users = userService.getInviteeIds(param.getUserId());
        for (String userId : users) {
            var inviteeTotal = sumUpOnlineAmount(SumUpParam.builder()
                    .userId(userId)
                    .dateString(param.getDateString())
                    .monthString(param.getMonthString())
                    .build());
            total = total.add(inviteeTotal.getValue1());
            ordinaryTotal = ordinaryTotal.add(inviteeTotal.getValue2());
        }

        BigDecimal selfTotal = BigDecimal.ZERO;

        try {
            List<ExecutionFlow> flows = flowService.getSucceedPayFlowsOfUserAndDate(param.getUserId(), param.getDateString()); // 个人交易额要加到返回里面 提供给上一级加总
            for (ExecutionFlow flow : flows) {
                if (!flow.getVipType().equals(User.VipType.PRIME.name())) { // 非皇冠才计入消费
                    total = total.add(flow.getAmount());
                    selfTotal = selfTotal.add(flow.getAmount());

                    if (!isVipInTime(userService.getUser(flow.getUserId()), flow.getCreateTime())) {
                        ordinaryTotal = ordinaryTotal.add(flow.getAmount());
                    }
                }
            }
        } catch (Throwable t) {
            // 表不存在是正常现象, 打个log忽略掉
            log.error("query table err" + t.getMessage());
        }


        flowSumUpMapper.storeUserAmount(TYPE_ONLINE, param.getMonthString(), param.getUserId(), param.getDateString(), total, selfTotal, ordinaryTotal);


        return new Tuple<>(total, ordinaryTotal);
    }

    public Tuple<BigDecimal, BigDecimal> sumUpPosAmount(SumUpParam param) {
        BigDecimal total = BigDecimal.ZERO;
        BigDecimal ordinaryTotal = BigDecimal.ZERO;

        List<String> users = userService.getInviteeIds(param.getUserId());
        for (String userId : users) {
            var inviteeTotal = sumUpPosAmount(SumUpParam.builder()
                    .userId(userId)
                    .dateString(param.getDateString())
                    .monthString(param.getMonthString())
                    .build());
            total = total.add(inviteeTotal.getValue1());
            ordinaryTotal = ordinaryTotal.add(inviteeTotal.getValue2());
        }
        BigDecimal selfTotal = BigDecimal.ZERO;
        try {
            var flows = leshuaExecutionFlowMapper.getSucceedFlowsOfUserAndDateString(param.getUserId(), param.getDateString()); // 个人交易额要加到返回里面 提供给上一级加总

            for (var flow : flows) {
                if (!User.VipType.PRIME.name().equals(flow.getVipType())) { // 非皇冠才计入消费
                    total = total.add(flow.getAmount());
                    selfTotal = selfTotal.add(flow.getAmount());

                    if (!isVipInTime(userService.getUser(flow.getUserId()), flow.getOrderTime())) {
                        ordinaryTotal = ordinaryTotal.add(flow.getAmount());
                    }
                }
            }
        } catch (Throwable t) {
            // 表不存在是正常现象, 打个log忽略掉
            log.error("query table err" + t.getMessage());
        }


        flowSumUpMapper.storeUserAmount(TYPE_POS, param.getMonthString(), param.getUserId(), param.getDateString(), total, selfTotal, ordinaryTotal);


        return new Tuple<>(total, ordinaryTotal);
    }

    public Tuple<BigDecimal, BigDecimal> sumUpUnionPayAmount(SumUpParam param) {
        BigDecimal total = BigDecimal.ZERO;
        BigDecimal ordinaryTotal = BigDecimal.ZERO;

        List<String> users = userService.getInviteeIds(param.getUserId());
        for (String userId : users) {
            var inviteeTotal = sumUpUnionPayAmount(SumUpParam.builder()
                    .userId(userId)
                    .dateString(param.getDateString())
                    .monthString(param.getMonthString())
                    .build());
            total = total.add(inviteeTotal.getValue1());
            ordinaryTotal = ordinaryTotal.add(inviteeTotal.getValue2());
        }

        BigDecimal selfTotal = BigDecimal.ZERO;

        try {
            var flows = unionPayExecutionFlowMapper.getSucceedPayFlowsOfUserAndDateString(param.getUserId(), param.getDateString()); // 个人交易额要加到返回里面 提供给上一级加总
            for (var flow : flows) {
                if (!flow.getVipType().equals(User.VipType.PRIME.name())) { // 非皇冠才计入消费
                    total = total.add(flow.getAmount());
                    selfTotal = selfTotal.add(flow.getAmount());

                    if (!isVipInTime(userService.getUser(flow.getUserId()), flow.getCreateTime())) {
                        ordinaryTotal = ordinaryTotal.add(flow.getAmount());
                    }
                }
            }
        } catch (Throwable t) {
            // 表不存在是正常现象, 打个log忽略掉
            log.error("query table err" + t.getMessage());

        }

        flowSumUpMapper.storeUserAmount(TYPE_UNION, param.getMonthString(), param.getUserId(), param.getDateString(), total, selfTotal, ordinaryTotal);


        return new Tuple<>(total, ordinaryTotal);
    }
    public MonthlySumUp getMonthlySumUp(String splittingType, String monthString, String userId) {
        return flowSumUpMapper.getSumUpByUserId(splittingType, monthString, userId);
    }

    // endregion


    // region 用户信息, 升级规则配置和工具类
    private static final BigDecimal getSplittingRates(String type, int level) {
        return splittingRates.get(type).get(level);
    }


    private static final HashMap<Integer, BigDecimal> onlineSplittingRates = new HashMap<Integer, BigDecimal>() {{
        put(0, BigDecimal.valueOf(10).divide(BigDecimal.valueOf(10000))); // 万10
        put(1, BigDecimal.valueOf(13).divide(BigDecimal.valueOf(10000))); // 万13
        put(2, BigDecimal.valueOf(14).divide(BigDecimal.valueOf(10000))); // 万14
        put(3, BigDecimal.valueOf(15).divide(BigDecimal.valueOf(10000))); // 万15
        put(4, BigDecimal.valueOf(16).divide(BigDecimal.valueOf(10000))); // 万16
        put(5, BigDecimal.valueOf(17).divide(BigDecimal.valueOf(10000))); // 万17
        put(6, BigDecimal.valueOf(18).divide(BigDecimal.valueOf(10000))); // 万18
        put(7, BigDecimal.valueOf(19).divide(BigDecimal.valueOf(10000))); // 万19
        put(8, BigDecimal.valueOf(20).divide(BigDecimal.valueOf(10000))); // 万20
        put(9, BigDecimal.valueOf(21).divide(BigDecimal.valueOf(10000))); // 万21
    }};

    private static final HashMap<Integer, BigDecimal> offlineSplittingRates = new HashMap<Integer, BigDecimal>() {{
        put(0, BigDecimal.valueOf(10).divide(BigDecimal.valueOf(10000))); // 万10
        put(1, BigDecimal.valueOf(10).divide(BigDecimal.valueOf(10000))); // 万10
        put(2, BigDecimal.valueOf(10.5).divide(BigDecimal.valueOf(10000))); // 万10.5
        put(3, BigDecimal.valueOf(11).divide(BigDecimal.valueOf(10000))); // 万11
        put(4, BigDecimal.valueOf(11.5).divide(BigDecimal.valueOf(10000))); // 万11.5
        put(5, BigDecimal.valueOf(12).divide(BigDecimal.valueOf(10000))); // 万12
        put(6, BigDecimal.valueOf(12.5).divide(BigDecimal.valueOf(10000))); // 万12.5
        put(7, BigDecimal.valueOf(13).divide(BigDecimal.valueOf(10000))); // 万13
        put(8, BigDecimal.valueOf(13.5).divide(BigDecimal.valueOf(10000))); // 万13.5
        put(9, BigDecimal.valueOf(14).divide(BigDecimal.valueOf(10000))); // 万14
    }};

    private static final Map<String, HashMap<Integer, BigDecimal>> splittingRates = new HashMap<String, HashMap<Integer, BigDecimal>>() {{
        put(TYPE_ONLINE, onlineSplittingRates);
        put(TYPE_OFFLINE, offlineSplittingRates);
    }};

    private int getTrueVipLevel(User user, Date theMoment, String monthString, MonthlySumUp sum) {

        if (isVipInTime(user, theMoment)) { // 当vip看, 按实际等级给到
            int trueVipLevel = sum.getVipLevel();
            return Math.max(Optional.ofNullable(user.getPurchasedVipLevel()).orElse(0), trueVipLevel);

        } else {
            return 0;
        }
    }

    private boolean isVipInTime(User user, Date theMoment) {
        return userService.isVipInTime(user, theMoment);

//        boolean inTempVipScale = (user.getVipType() == User.VipType.ORDINARY || (user.getVipType() == User.VipType.PAYED && user.getVipSubType() == User.VipSubType.TEMP))
//                && DateUtils.isBetween(theMoment, user.getVipStartTime(), user.getVipValidation()); // 是临时vip, 或普通会员, 但是这个时间点在vip生效区间内
//
//        boolean isPermanentVip = (user.getVipType() == User.VipType.PAYED || user.getVipType() == User.VipType.PRIME) && user.getVipSubType() == User.VipSubType.PERMANENT;
//
//        return isPermanentVip || inTempVipScale;
    }
    // endregion

    // region 计算分润
    @Async
    public void sumUpSplitting(String date, String monthString) {
        sumUpSplittingOnline(date, monthString);
        sumUpSplittingOffline(date, monthString);
    }

    public void sumUpSplittingOffline(String date, String monthString) {

        try {
            var flows = leshuaExecutionFlowMapper.getSucceedPayFlowsAndDateString(date);
            for (var flow : flows) {
                String invitorId = userService.getInvitorId(flow.getUserId());
                MonthlySumUp sum = flowSumUpMapper.getSumUpByUserId(TYPE_OFFLINE, monthString, flow.getUserId());
                int flowGeneratorVipLevel = getTrueVipLevel(userService.getUser(flow.getUserId()), flow.getOrderTime(), monthString, sum);
                // 推广奖, 管理奖计算按起点用户的交易量等级和修正后等级的最大值取. (考虑买了高级vip的代理商的情况)
                sumUpInviteChain(TYPE_OFFLINE, invitorId, Math.max(sum.getVipLevel(), flowGeneratorVipLevel), ExecutionFlowForCal.from(flow), monthString, true);
            }

            var yinShengflows = unionPayExecutionFlowMapper.getSucceedPayFlowsAndDateString(date);
            for (var flow : yinShengflows) {
                String invitorId = userService.getInvitorId(flow.getUserId());
                MonthlySumUp sum = flowSumUpMapper.getSumUpByUserId(TYPE_OFFLINE, monthString, flow.getUserId());
                int flowGeneratorVipLevel = getTrueVipLevel(userService.getUser(flow.getUserId()), flow.getCreateTime(), monthString, sum);
                // 推广奖, 管理奖计算按起点用户的交易量等级和修正后等级的最大值取. (考虑买了高级vip的代理商的情况)
                sumUpInviteChain(TYPE_OFFLINE, invitorId, Math.max(sum.getVipLevel(), flowGeneratorVipLevel), ExecutionFlowForCal.from(flow), monthString, true);
            }
        } catch (BadSqlGrammarException ignore) {
            // 不需要处理, 这天没交易, 不存在那张表而已
        }
    }

    public void sumUpSplittingOnline(String date, String monthString) {

        try {
            List<ExecutionFlow> flows = flowService.getSucceedPayFlowsAndDateString(date);
            for (ExecutionFlow flow : flows) {
                String invitorId = userService.getInvitorId(flow.getUserId());
                MonthlySumUp sum = flowSumUpMapper.getSumUpByUserId(TYPE_ONLINE, monthString, flow.getUserId());
                int flowGeneratorVipLevel = getTrueVipLevel(userService.getUser(flow.getUserId()), flow.getCreateTime(), monthString, sum);
                // 推广奖, 管理奖计算按起点用户的交易量等级和修正后等级的最大值取. (考虑买了高级vip的代理商的情况)
                sumUpInviteChain(TYPE_ONLINE, invitorId, Math.max(sum.getVipLevel(), flowGeneratorVipLevel), ExecutionFlowForCal.from(flow), monthString, true);
            }
        } catch (BadSqlGrammarException ignore) {
            // 不需要处理, 这天没交易, 不存在那张表而已
        }
    }

    private void sumUpInviteChain(String splittingType, String invitorId, int lastVipLevel, ExecutionFlowForCal flow, String monthString, boolean calDirect) {

        if ("PRIME".equals(flow.getVipType())) { // 皇冠交易量不计入
            return;
        }
        User invitor = null;
        try {
            invitor = userService.getUser(invitorId);
        } catch (ServiceException e) {
            return; // 有报错说明没有邀请人, 说明整个计算到头了
        }



        MonthlySumUp invitorSum = flowSumUpMapper.getSumUpByUserId(splittingType, monthString, invitor.getId());
        int currentLevel = getTrueVipLevel(invitor, flow.getCreateTime(), monthString, invitorSum);

        if (calDirect) {
            BigDecimal directSplitting = BigDecimal.ZERO;
            if (!isVipInTime(userService.getUser(flow.getUserId()), flow.getCreateTime())) { // 本条交易是普通会员产生的交易才允许有直营奖计算

                // 直营奖最高按vip5
                // 直营奖按原始分润比例算
                int splittingLevel = Math.min(5, VipLevelCounter.getRawVipLevel(invitorSum.getOrdinaryAmount(), 0));
                if (!isVipInTime(invitor, flow.getCreateTime())) { // 交易发生时上级如果是普通会员, 一律按 0 级算直营奖
                    splittingLevel = 0;
                }


                splittingLevel = 0; // 20200508更改: 直营奖收益公式调整为：直营奖金=直营普通用户交易额×万10；
                                    // 该公式适用于所有用户，包含普通用户、VIP用户等。原对应VIP或皇冠VIP用的直营奖公式因重复核算交易量分润是错误的，废掉。
                // 直营奖计算
                directSplitting = flow.getAmount().multiply(getSplittingRates(splittingType, splittingLevel));

            }

            flowSumUpMapper.insertSplittingFlow(
                    splittingType,
                    monthString,
                    SplittingFlow.builder()
                            .type(SplittingFlow.Type.DIRECT)
                            .amount(flow.getAmount())
                            .executionFlowId(flow.getId())
                            .splitting(directSplitting)
                            .userId(invitorId)
                            .currentVipLevel(currentLevel)
                            .tradeTime(flow.getCreateTime())
                            .build());

        }

        BigDecimal expandingSplitting = BigDecimal.ZERO;
        BigDecimal managingSplitting = BigDecimal.ZERO;
        if (currentLevel > lastVipLevel) {

            BigDecimal expandingLastRate = getSplittingRates(splittingType, Math.min(lastVipLevel, 5));
            BigDecimal expandingCurrentRate = getSplittingRates(splittingType, Math.min(currentLevel, 5));

            if (expandingLastRate.subtract(expandingCurrentRate).compareTo(BigDecimal.ZERO) != 0) {

                expandingSplitting = flow.getAmount().multiply(expandingCurrentRate.subtract(expandingLastRate));


            }

            // 管理奖计算
            BigDecimal managingLastRate = getSplittingRates(splittingType, Math.max(lastVipLevel, 5));
            BigDecimal managingCurrentRate = getSplittingRates(splittingType, Math.max(currentLevel, 5));
            if (managingCurrentRate.subtract(managingLastRate).compareTo(BigDecimal.ZERO) != 0) {
                managingSplitting = flow.getAmount().multiply(managingCurrentRate.subtract(managingLastRate));



            }

        } else {
            // 前面有人分润比率比自己高或持平, 这比不参与计算
        }

        flowSumUpMapper.insertSplittingFlow(
                splittingType,
                monthString,
                SplittingFlow.builder()
                        .type(SplittingFlow.Type.EXPANDING)
                        .amount(flow.getAmount())
                        .executionFlowId(flow.getId())
                        .splitting(expandingSplitting)
                        .userId(invitorId)
                        .currentVipLevel(currentLevel)
                        .tradeTime(flow.getCreateTime())
                        .build());

        flowSumUpMapper.insertSplittingFlow(
                splittingType,
                monthString,
                SplittingFlow.builder()
                        .type(SplittingFlow.Type.MANAGING)
                        .amount(flow.getAmount())
                        .executionFlowId(flow.getId())
                        .splitting(managingSplitting)
                        .userId(invitorId)
                        .currentVipLevel(currentLevel)
                        .tradeTime(flow.getCreateTime())
                        .build());

        sumUpInviteChain(splittingType, userService.getInvitorId(invitorId), Math.max(currentLevel, lastVipLevel), flow, monthString, false);

    }

    // endregion


    // region 统计月流水
    @Async
    public void calculateMonthlyAmount(String monthString, boolean shouldCalcSplitting) {
        calculateMonthlyAmountOf(Arrays.asList(TYPE_ONLINE), TYPE_ONLINE, monthString);
        calculateMonthlyAmountOf(Arrays.asList(TYPE_POS), TYPE_POS, monthString);
        calculateMonthlyAmountOf(Arrays.asList(TYPE_UNION), TYPE_UNION, monthString);
        calculateMonthlyAmountOf(Arrays.asList(TYPE_POS, TYPE_UNION), TYPE_OFFLINE, monthString);

        if (!shouldCalcSplitting) return;
        flowSumUpMapper.clearSplitting(TYPE_ONLINE, monthString);
        flowSumUpMapper.clearSplitting(TYPE_OFFLINE, monthString);


        Calendar c = DateUtils.calendarFromMonthString(monthString);
        int month = c.get(Calendar.MONTH);
        do {
            String dateString = DateUtils.getDateString(c);
            mqService.sendMsg(
                    MQMessage.builder()
                            .type(MQMessage.T_CALC_FLOW_SPLITTINGS)
                            .content(
                                    SumUpParam.builder()
                                            .dateString(dateString)
                                            .monthString(monthString)
                                            .build())
                            .build());

            c.add(Calendar.DAY_OF_MONTH, 1);
        } while (c.get(Calendar.MONTH) == month);
    }

    public void startOutputIncomeReport(String monthString) {
        List<String> userIds = userService.getAllUserId();
        for (var userId : userIds) {
            var message =  MQMessage.builder()
                    .type(MQMessage.T_OUTPUT_INCOME_REPORT)
                    .content(SumUpParam.builder()
                            .monthString(monthString)
                            .userId(userId)
                            .build())
                    .build();

            mqService.sendMsg(message);
        }

    }

    public void startOutputIncomeSummaryReport(String monthString) {
        List<String> userIds = userService.getAllUserId();
        for (var userId : userIds) {
            var message =  MQMessage.builder()
                    .type(MQMessage.T_OUTPUT_INCOME_SUMMARY_REPORT)
                    .content(SumUpParam.builder()
                            .monthString(monthString)
                            .userId(userId)
                            .build())
                    .build();

            mqService.sendMsg(message);
        }

        redis.boundValueOps(SUMMARY_MONTH).set(monthString);

    }

    // endregion

    // region 月度交易量统计和vip等级核算
    private void calculateMonthlyAmountOf(List<String> splittingTypes, String monthlyStoreType, String monthString) {
        List<String> userIds = userService.getAllUserId();
        flowSumUpMapper.clearMonthlySumUp(monthlyStoreType, monthString);

        for (String userId : userIds) {
            var sumUp = new MonthlySumUp();
            sumUp.setUserId(userId);
            sumUp.setAmount(BigDecimal.ZERO);
            sumUp.setSelfAmount(BigDecimal.ZERO);
            sumUp.setOrdinaryAmount(BigDecimal.ZERO);
            for (var splittingType : splittingTypes) {
                var singleResult = flowSumUpMapper.sumUpUserId(splittingType, userId, monthString); // 统计每个userId的本月总交易额
                if (singleResult != null) {
                    sumUp.setAmount(sumUp.getAmount().add(singleResult.getAmount()));
                    sumUp.setSelfAmount(sumUp.getSelfAmount().add(singleResult.getSelfAmount()));
                    sumUp.setOrdinaryAmount(sumUp.getOrdinaryAmount().add(singleResult.getOrdinaryAmount()));

                }
            }
            flowSumUpMapper.insertMonthlySumUp(monthlyStoreType, sumUp, monthString); // 写表
        }


        List<String> rootUsers = userService.getRootUserIds();
        for (String userId : rootUsers) {
            processVipLevel(monthlyStoreType, VipLevelCounter.builder().build(), userId, monthString);
        }
    }



    private void processVipLevel(String splittingType, VipLevelCounter counter, String userId, String monthString) {
        List<String> invitees = userService.getInviteeIds(userId); // 先把自己的下线都找出来算他们的 vip 等级
        for (String u : invitees) {
            processVipLevel(splittingType, counter, u, monthString);
        }

        MonthlySumUp sum = flowSumUpMapper.getSumUpByUserId(splittingType, monthString, userId);

        int selfV5Count = VipLevelCounter.getRawVipLevel(sum.getSelfAmount(), 0) >= 5 ? 1 : 0; // 计算自己的刷卡额是否有到达v5线, 有的话, 需要 +1 v5 数 (不对上级生效
        int totalV5Count = counter.getVip5Count() + selfV5Count;
        Integer vipLevel = VipLevelCounter.getRawVipLevel(sum.getAmount(), totalV5Count);
        sum.setVipLevel(vipLevel);
        sum.setVip5Count(totalV5Count);
        if (vipLevel >= 5) {
            counter.setVip5Count(counter.getVip5Count() + 1);
        }
        flowSumUpMapper.modifySumUp(splittingType, monthString, sum);
    }

    // endregion


    // region 收益报告部分

    public String getReportDetailKey(String monthString, String userId) {
        return "report/detail/" + userId + "/" + monthString + ".json";
    }

    public String getCurrentSummaryMonthString() {
        return redis.boundValueOps(SUMMARY_MONTH).get();
    }

    public String getReportSummaryKey(String userId) {
        return getReportSummaryKey(getCurrentSummaryMonthString(), userId);
    }


    public String getReportSummaryKey(String monthString, String userId) {
        return "report/summary/" + userId + "/" + monthString + ".json";
    }

    public void outputIncomeReport(String monthString, String userId) {
        var user = userService.getUser(userId);

        List<IncomeDetailItemVo> incomeDetailItemVos = new ArrayList<>();
        var resp = IncomeDetailVo.builder()
                .items(incomeDetailItemVos)
                .build();

        fillActivateIncome(resp, monthString, user);
        fillOnlineIncome(resp, monthString, user);
        fillOfflineIncome(resp, monthString, user);

        fillCpsIncome(resp, monthString, user);
        fillGlobalBonusIncome(resp, monthString, user);
        fillAdj(resp, monthString, user);

        String reportJson = JSONUtil.toJson(resp);
        objectiveStorageService.putObject(getReportDetailKey(monthString, user.getId()), reportJson.getBytes(StandardCharsets.UTF_8));

        var additionalInfo = user.getId() + "." + monthString;

        BigDecimal totalIncome = resp.getOnlineIncome().add(resp.getOfflineIncome()).add(resp.getAdjustmentIncome());
        if (totalIncome.compareTo(BigDecimal.ZERO) <= 0) { // 分润小于等于0不发通知也不添加流水
            return;
        }
        if (!balanceService.hasPublishedIncome(BalanceFlow.Type.TRADING, additionalInfo)) {
            balanceService.insertUserBalance(BalanceFlow.builder()
                    .userId(user.getId())
                    .additionalInfos(additionalInfo)
                    .amount(totalIncome)
                    .type(BalanceFlow.Type.TRADING)
                    .description(new HashMap<String, String>() {{
                        put("desc", "交易奖金发放");
                    }})
                    .build());

            messageService.addMessage(Message.builder()
                    .userId(userId)
                    .title(monthString.substring(4, 6) + "月分润奖金已发放")
                    .content("请核对您钱包中的到账情况, 如有疑问请{{联系客服}}")
                    .type(Message.Type.BONUS)
                    .build(), true);
        }

    }


    private DecimalFormat df = new DecimalFormat("'¥' #,##0.00");

    private void fillOfflineIncome(IncomeDetailVo vo, String monthString, User user) {
        List<IncomeDetailItemVo> incomeDetailItemVos = vo.getItems();

        var offlineDirect = getSplitting(TYPE_OFFLINE, monthString, user.getId(), SplittingFlow.Type.DIRECT);
        var offlineExpanding = getSplitting(TYPE_OFFLINE, monthString, user.getId(), SplittingFlow.Type.EXPANDING);
        var offlineManaging = getSplitting(TYPE_OFFLINE, monthString, user.getId(), SplittingFlow.Type.MANAGING);

        var posAmount = BigDecimal.ZERO;
        var unionPayAmount = BigDecimal.ZERO;
        try {
            posAmount = ObjectUtils.defaultIfNull(getMonthlySumUp(TYPE_POS, monthString, user.getId()), new MonthlySumUp()).getAmount();
        } catch (Throwable t) {
            // 拿不到就0 忽略错误
        }

        try {
            unionPayAmount = ObjectUtils.defaultIfNull(getMonthlySumUp(TYPE_UNION, monthString, user.getId()), new MonthlySumUp()).getAmount();
        } catch (Throwable t) {
            // 拿不到就0 忽略错误
        }


        var offlineSplitting = offlineDirect.getSplitting().add(offlineExpanding.getSplitting()).add(offlineManaging.getSplitting());

        vo.setOfflineIncome(offlineSplitting);
        vo.setOfflineDirectlyAmount(offlineDirect.getAmount());
        vo.setOfflineIndirectlyAmount(unionPayAmount.add(posAmount).subtract(offlineDirect.getAmount()));

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H1)
                .leftText("支付线下业务")
                .middleText(df.format(unionPayAmount.add(posAmount)))
                .rightText(df.format(offlineSplitting))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("MPOS")
                .middleText(df.format(posAmount))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("闪付扫码")
                .middleText(df.format(unionPayAmount))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("直营奖")
                .middleText(df.format(offlineDirect.getAmount()))
                .rightText(df.format(offlineDirect.getSplitting()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("推广奖")
                .middleText(df.format(offlineExpanding.getAmount()))
                .rightText(df.format(offlineExpanding.getSplitting()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("管理奖")
                .middleText(df.format(offlineManaging.getAmount()))
                .rightText(df.format(offlineManaging.getSplitting()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("园丁奖")
                .rightText(offlineManaging.getCurrentVipLevel() == 6 ? df.format(offlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .build());


        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("导师奖")
                .rightText(offlineManaging.getCurrentVipLevel() == 7 ? df.format(offlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .build());


        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("领袖奖")
                .rightText(offlineManaging.getCurrentVipLevel() == 8 ? df.format(offlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .build());


        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("荣誉奖")
                .rightText(offlineManaging.getCurrentVipLevel() == 9 ? df.format(offlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .needBottomMargin(true)
                .needBottomPadding(true)
                .build());

    }

    private void fillOnlineIncome(IncomeDetailVo vo, String monthString, User user) {
        List<IncomeDetailItemVo> incomeDetailItemVos = vo.getItems();
        var onlineDirect = getSplitting(TYPE_ONLINE, monthString, user.getId(), SplittingFlow.Type.DIRECT);
        var onlineExpanding = getSplitting(TYPE_ONLINE, monthString, user.getId(), SplittingFlow.Type.EXPANDING);
        var onlineManaging = getSplitting(TYPE_ONLINE, monthString, user.getId(), SplittingFlow.Type.MANAGING);
        var onlineAmount = BigDecimal.ZERO;
        try {
            onlineAmount = ObjectUtils.defaultIfNull(getMonthlySumUp(TYPE_ONLINE, monthString, user.getId()), new MonthlySumUp()).getAmount();
        } catch (Throwable t) {
            // 没有就保持0, 不报错
        }
        var onlineSplitting = onlineDirect.getSplitting().add(onlineExpanding.getSplitting()).add(onlineManaging.getSplitting());

        vo.setOnlineIncome(onlineSplitting);
        vo.setOnlineDirectlyAmount(onlineDirect.getAmount());
        vo.setOnlineIndirectlyAmount(onlineAmount.subtract(onlineDirect.getAmount()));

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H1)
                .leftText("支付线上业务")
                .middleText(df.format(onlineAmount))
                .rightText(df.format(onlineSplitting))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("快收业务")
                .middleText(df.format(onlineAmount))
                .rightText(df.format(onlineSplitting))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("直营奖")
                .middleText(df.format(onlineDirect.getAmount()))
                .rightText(df.format(onlineDirect.getSplitting()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("推广奖")
                .middleText(df.format(onlineExpanding.getAmount()))
                .rightText(df.format(onlineExpanding.getSplitting()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("管理奖")
                .middleText(df.format(onlineManaging.getAmount()))
                .rightText(df.format(onlineManaging.getSplitting()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("园丁奖")
                .rightText(onlineManaging.getCurrentVipLevel() == 6 ? df.format(onlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .build());


        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("导师奖")
                .rightText(onlineManaging.getCurrentVipLevel() == 7 ? df.format(onlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .build());


        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("领袖奖")
                .rightText(onlineManaging.getCurrentVipLevel() == 8 ? df.format(onlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .build());


        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H4)
                .leftText("荣誉奖")
                .rightText(onlineManaging.getCurrentVipLevel() == 9 ? df.format(onlineManaging.getSplitting()) : df.format(BigDecimal.ZERO))
                .needBottomMargin(true)
                .needBottomPadding(true)
                .build());
    }

    private void fillActivateIncome(IncomeDetailVo vo, String monthString, User user) {
        List<IncomeDetailItemVo> incomeDetailItemVos = vo.getItems();
        var totalAmount = new AtomicReference<>(BigDecimal.ZERO);
        var totalBonus = new AtomicReference<>(BigDecimal.ZERO);

        var directiveVip = new AtomicReference<>(new Tuple<>(0, BigDecimal.ZERO));
        var indirectiveVip = new AtomicReference<>(new Tuple<>(0, BigDecimal.ZERO));

        var directivePrime = new AtomicReference<>(new Tuple<>(0, BigDecimal.ZERO));

        var bonuses = Optional.ofNullable(activateBonusMapper.bonusesOfUser(user.getId(), monthString)).orElseGet(ArrayList::new);
        var sp = bonuses.parallelStream().collect(Collectors.groupingBy(ActivateBonus::getStatisticType));

        var directiveBonuses = Optional.ofNullable(sp.get(ActivateBonus.StatisticType.DIRECTIVE)).orElseGet(ArrayList::new);
        var indirectiveBonuses = Optional.ofNullable(sp.get(ActivateBonus.StatisticType.INDIRECTIVE)).orElseGet(ArrayList::new);


        var vipGrouped = directiveBonuses.parallelStream()
                .collect(Collectors.groupingBy(ActivateBonus::getType));

        vipGrouped.forEach((type, v) -> {
            v.forEach((bonus) -> {
                log.info("sum act");
                log.info(bonus);

                totalAmount.set(totalAmount.get().add(bonus.getAmount()));
                totalBonus.set(totalBonus.get().add(bonus.getBonus()));

                Tuple<Integer, BigDecimal> tuple = null;
                if (type == User.VipType.PAYED) {
                    tuple = directiveVip.get();
                } else {
                    tuple = directivePrime.get();
                }

                tuple.setValue1(tuple.getValue1() + 1);
                tuple.setValue2(tuple.getValue2().add(bonus.getBonus()));
            });
        });

        indirectiveBonuses.parallelStream().forEach(bonus -> {
            var tuple = indirectiveVip.get();
            if (bonus.getAmount().compareTo(BigDecimal.ZERO) == 0) { // amount != 0 说明是每次的2块钱, 不计入总量 == 0 是每次月底活动结算, 作为总量
                tuple.setValue1(bonus.getCount());
            }
            tuple.setValue2(tuple.getValue2().add(bonus.getBonus()));
            totalBonus.set(totalBonus.get().add(bonus.getBonus()));
        });

        Calendar c = DateUtils.calendarFromMonthString(monthString);


//        var certificatingFollowerCount = userService.certificatingFollowerCount(user.getId(), DateUtils.getFirstDayFirstSecondThisMonth(c), DateUtils.getLastDayLastSecondThisMonth(c));
//
//        var certificatingBonus = new BigDecimal(3 * certificatingFollowerCount);
//        totalBonus.set(totalBonus.get().add(certificatingBonus));


        var cpsLevel = cpsService.getCpsLevel(user.getId());
        vo.setActivateIncome(totalBonus.get());
        vo.setCpsActivelyFollowerCount(cpsLevel.getActiveCount());
        vo.setCpsInactivelyFollowerCount(cpsLevel.getFollowersCount() - cpsLevel.getActiveCount());
        vo.setInvitePrimeCount((long)directiveVip.get().getValue1() + indirectiveVip.get().getValue1() + directivePrime.get().getValue1());
        vo.setInviteCount(userService.getInviteeCountRegisterAfter(user.getId(), DateUtils.getFirstDayFirstSecondThisMonth(c)));

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H1)
                .leftText("激活奖")
                .middleText(df.format(totalAmount.get()))
                .rightText(df.format(totalBonus.get()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("激活VIP")
                .middleText(directiveVip.get().getValue1() + indirectiveVip.get().getValue1() + "")
                .rightText(df.format(directiveVip.get().getValue2().add(indirectiveVip.get().getValue2())))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("直推")
                .middleText(directiveVip.get().getValue1() + "")
                .rightText(df.format(directiveVip.get().getValue2()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("激活皇冠")
                .middleText(directivePrime.get().getValue1() + "")
                .rightText(df.format(directivePrime.get().getValue2()))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("直推")
                .middleText(directivePrime.get().getValue1() + "")
                .rightText(df.format(directivePrime.get().getValue2()))
                .build());


//        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
//                .fontSize(IncomeDetailItemVo.FontSize.H2)
//                .leftText("激励金")
//                .middleText(certificatingFollowerCount + "人")
//                .rightText(df.format(certificatingBonus))
//                .needBottomPadding(true)
//                .needBottomMargin(true)
//                .build());

    }

    public void fillCpsIncome(IncomeDetailVo vo, String monthString, User user) {
        List<IncomeDetailItemVo> incomeDetailItemVos = vo.getItems();

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H1)
                .leftText("CPS业务")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("淘宝CPS")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H3)
                .leftText("信用卡推广")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("自购返佣")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("推广返佣")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("管理奖金")
                .rightText(df.format(BigDecimal.ZERO))
                .needBottomPadding(true)
                .needBottomMargin(true)
                .build());
    }

    public void fillGlobalBonusIncome(IncomeDetailVo vo, String monthString, User user) {
        List<IncomeDetailItemVo> incomeDetailItemVos = vo.getItems();

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H1)
                .leftText("全球分红")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("支付线上业务")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("支付线下业务")
                .rightText(df.format(BigDecimal.ZERO))
                .needBottomPadding(true)
                .needBottomMargin(true)
                .build());
    }

    public void fillAdj(IncomeDetailVo vo, String monthString, User user) {
        List<IncomeDetailItemVo> incomeDetailItemVos = vo.getItems();

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H1)
                .leftText("调整金额")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("扣款金额")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("补发金额")
                .rightText(df.format(BigDecimal.ZERO))
                .build());

        incomeDetailItemVos.add(IncomeDetailItemVo.builder()
                .fontSize(IncomeDetailItemVo.FontSize.H2)
                .leftText("奖金补贴")
                .rightText(df.format(BigDecimal.ZERO))
                .needBottomPadding(true)
                .needBottomMargin(true)
                .build());
    }

    public void outputIncomeSummaryReport(String monthString, String userId) {
        var maxDay = Integer.parseInt(monthString);

        var prefix = getReportDetailKey("", userId).replace(".json", "");
        var keys = objectiveStorageService.ls(prefix);

        // 把所有的文件名, 去掉头尾并过滤比给定的monthString大的day(在日报已出但是没有审核的时候会出现), 并排序输出
        var months = keys.parallelStream()
                .map(key -> Integer.parseInt(key
                        .replace(prefix, "")
                        .replace(".json", "")))
                .filter(month -> maxDay >= month)
                .sorted(Comparator.reverseOrder())
                .map(Object::toString)
                .collect(Collectors.toList());

        var total = new IncomeSummaryVo();

        IncomeYearlyItemVo currentYearVo = null;
        String currentYear = "";
        for (var month : months) {
            var year = month.substring(0,4);
            if (!year.equals(currentYear)) {
                currentYear = year;
                currentYearVo = new IncomeYearlyItemVo();
                total.getPerYear().add(currentYearVo);
                currentYearVo.setTitle(year + "年");
            }
            var detail = getReport(month, userId);

            var income = detail.getActivateIncome()
                    .add(detail.getCpsIncome())
                    .add(detail.getOfflineIncome())
                    .add(detail.getOnlineIncome());

            currentYearVo.getPerMonth().add(IncomeMonthlyItemVo.builder()
                    .title(month.substring(4,6) + "月")
                    .income(income)
                    .build());

            total.setTotal(total.getTotal().add(income));
        }

        objectiveStorageService.putObject(
                getReportSummaryKey(monthString, userId),
                JSONUtil.toJson(total).getBytes(StandardCharsets.UTF_8)
        );

    }

    private IncomeDetailVo getReport(String monthString, String userId) {
        var url = objectiveStorageService.signUrl("https://private.static.ylmvip.cn/" + getReportDetailKey(monthString, userId));
        var req = new Request.Builder().url(url).get().build();
        try {
            var resp = ClientHolder.getClient().newCall(req).execute();
            if (resp.body() != null) {
                var json = resp.body().string();
                return JSONUtil.parseJson(json, IncomeDetailVo.class);
            }
        } catch (Throwable t) {
            log.warn("request to:" + url + "err", t);
        }

        return new IncomeDetailVo();

    }

    // endregion

    // region 周度收益预估
    private final static int DAYS_BACK_OFF_TO_SUM_INCOME = 7;
    public void sumUpApproximatelyIncome(String userId, String dateString) {
        var c = DateUtils.calendarFromDateString(dateString);
        var monthString = DateUtils.getMonthDateString(c);
        var monthlyIncome = BigDecimal.ZERO;
        monthlyIncome = monthlyIncome.add(getSplitting(TYPE_ONLINE, monthString, userId, null, null).getSplitting());
        monthlyIncome = monthlyIncome.add(getSplitting(TYPE_OFFLINE, monthString, userId, null, null).getSplitting());

        var scales = DateUtils.getBackoffScales(c, DAYS_BACK_OFF_TO_SUM_INCOME);

        var startDateString = "";
        var backOffIncome = BigDecimal.ZERO;
        for (var scale : scales) {
            backOffIncome = backOffIncome.add(getSplitting(TYPE_ONLINE, scale.getMonthString(), userId, scale.getStartTime(), scale.getEndTime()).getSplitting());
            backOffIncome = backOffIncome.add(getSplitting(TYPE_OFFLINE, scale.getMonthString(), userId, scale.getStartTime(), scale.getEndTime()).getSplitting());

            startDateString = DateUtils.getDateString(scale.getStartTime());
        }


        var endTime = new Date();
        c.set(Calendar.DAY_OF_MONTH, 1);
        DateUtils.setFirstTimeOfDay(c);
        var startTime = c.getTime();

        var invitees = userService.getInviteeIds(userId);
        var newVipCount = userService.getVipStartInRangeCount(invitees, startTime, endTime);
        var income = UserApproximatelyIncome.builder()
                .userId(userId)
                .backOffIncome(backOffIncome)
                .backOffScale(startDateString + "-" + dateString)
                .monthlyIncome(monthlyIncome)
                .newVipCount(newVipCount)
                .build();

        flowSumUpMapper.insertUserApproximatelyIncome(income);


    }

    public UserApproximatelyIncome getUserApproximatelyIncome(String userId) {
        try {
            var income = flowSumUpMapper.userApproximatelyIncome(userId);
            if (income != null) {
                return income;
            }
        } catch (Throwable t) {

        }

        return UserApproximatelyIncome.builder()
                .userId(userId)
                .backOffIncome(BigDecimal.ZERO)
                .backOffScale("暂无数据")
                .monthlyIncome(BigDecimal.ZERO)
                .build();
    }
    // endregion

    // region 激活奖
    private final static Map<Integer, BigDecimal> activateBonusRate = new HashMap<Integer, BigDecimal>() {{
        put(0, BigDecimal.ZERO);
        put(1, new BigDecimal("0.2"));
        put(2, new BigDecimal("0.3"));
        put(3, new BigDecimal("0.3"));
        put(4, new BigDecimal("0.4"));
        put(5, new BigDecimal("0.4"));
        put(6, new BigDecimal("0.5"));
        put(7, new BigDecimal("0.5"));
        put(8, new BigDecimal("0.6"));
        put(9, new BigDecimal("0.6"));

    }};

    @Async
    public void publishIdentifyBonus(User certificatingUser) {

        balanceService.insertUserBalance(BalanceFlow.builder()
                .type(BalanceFlow.Type.ACTIVATE_BONUS)
                .amount(BigDecimal.ONE)
                .description(new HashMap<String, String>(){{
                    put("desc", "实名认证激活奖金");
                }})
                .userId(certificatingUser.getId())
                .occurTime(new Date())
                .additionalInfos("ACTIVATE.CERTIFICATE.SELF")
                .build());


        messageService.addMessage(Message.builder()
                .userId(certificatingUser.getId())
                .type(Message.Type.BONUS)
                .content("恭喜您实名认证成功获得1元激活奖金红包，分享好友还可获更多红包。如有疑问请{{联系客服}}。")
                .title("激活奖发放通知")
                .build(), true);


        var invitor = userService.getInvitor(certificatingUser.getId());

        if (invitor != null) {
            var name = userService.getUserNameByUserId(certificatingUser.getId());

            balanceService.insertUserBalance(BalanceFlow.builder()
                    .type(BalanceFlow.Type.ACTIVATE_BONUS)
                    .amount(new BigDecimal("3"))
                    .description(new HashMap<String, String>(){{
                        put("desc", "粉丝实名认证激活奖金");
                    }})
                    .userId(invitor.getId())
                    .occurTime(new Date())
                    .additionalInfos("ACTIVATE.CERTIFICATE.FOLLOWER." + certificatingUser.getId())
                    .build());


            messageService.addMessage(Message.builder()
                    .userId(invitor.getId())
                    .type(Message.Type.BONUS)
                    .content("用户" + name + "为您贡献了一笔3元激活奖金收益，分享好友还可获更多收益。如有疑问请{{联系客服}}。")
                    .title("激活奖发放通知")
                    .build(), true);
        }


    }

//    @Transactional
    @Async
    public void clacActivateBonus(User activatingUser, BigDecimal amount, User.VipType vipType) {
        if (vipType == User.VipType.ORDINARY) return;

        var invitor = userService.getInvitor(activatingUser.getId());

        if (invitor == null) return;
        var lastMonthLastMoment = DateUtils.getMonthLastDayLastSecond(DateUtils.getLastMonthFirstDay().getTime());
        var isVipLastMonth = isVipInTime(invitor, lastMonthLastMoment); // 上个月最后 1 ms是不是会员
        var isVipNow = isVipInTime(invitor, new Date());

        var isPermanentVipLastMonth = userService.isPermanentVipInTime(invitor, lastMonthLastMoment);
        var isPermanentVipNow = userService.isPermanentVipInTime(invitor, new Date());


        var vipLevelForCalc = 0;
        if ((!isPermanentVipLastMonth && isPermanentVipNow) || (isVipNow && !isVipLastMonth)){
            // 按VIP1计算激活奖
            vipLevelForCalc = 1;
        } else if (isVipLastMonth){
            // 取上月 vip 等级计算激活奖

            var vipOffline = Optional.ofNullable(getMonthlySumUp(TYPE_OFFLINE, DateUtils.getLastMonthString(), invitor.getId())).map(MonthlySumUp::getVipLevel).orElse(1);
            var vipOnline = Optional.ofNullable(getMonthlySumUp(TYPE_ONLINE, DateUtils.getLastMonthString(), invitor.getId())).map(MonthlySumUp::getVipLevel).orElse(1);
            var vipCps = Optional.ofNullable(cpsService.getCpsLevelInDate(invitor.getId(), DateUtils.getDateString(lastMonthLastMoment))).map(CpsLevel::getLevel).orElse(1L).intValue();

            vipLevelForCalc = MathUtils.max(vipOffline, vipOnline, vipCps);
        }
        vipLevelForCalc = MathUtils.max(vipLevelForCalc, Optional.ofNullable(invitor.getPurchasedVipLevel()).orElse(0));

        if (isVipNow) { // 20200522 激活奖, 只要是vip的, 顶格计算
            vipLevelForCalc = 9;
        }

        if (vipLevelForCalc == 0) return;

        var bonus = amount.multiply(activateBonusRate.get(vipLevelForCalc));

        activateBonusMapper.insert(ActivateBonus.builder()
                .userId(invitor.getId())
                .amount(amount)
                .bonus(bonus)
                .count(1)
                .statisticType(ActivateBonus.StatisticType.DIRECTIVE)
                .time(new Date())
                .type(vipType)
                .build(), DateUtils.getCurrentMonthString());

        balanceService.insertUserBalance(BalanceFlow.builder()
                .type(BalanceFlow.Type.ACTIVATE_BONUS)
                .amount(bonus)
                .description(new HashMap<String, String>(){{
                    put("desc", vipType == User.VipType.PRIME ? "皇冠会员激活奖金发放" : "会员激活奖金发放");
                }})
                .userId(invitor.getId())
                .occurTime(new Date())
                .additionalInfos("ACTIVATE.INVITEE." + vipType.name() + "." + activatingUser.getId())
                .build());


        var name = userService.getUserNameByUserId(activatingUser.getId());
        messageService.addMessage(Message.builder()
                .userId(invitor.getId())
                .type(Message.Type.BONUS)
                .content("用户" + name + "给您贡献了一笔" + df.format(bonus) + "元的激活奖金收益，如有疑问请{{联系客服}}。")
                .title("激活奖发放通知")
                .build(), true);

        // 20200509 干掉间推奖
//        var grandInvitor = userService.getInvitor(invitor.getId());
//        if (grandInvitor != null) {
//            activateBonusMapper.insert(ActivateBonus.builder()
//                    .userId(grandInvitor.getId())
//                    .amount(amount)
//                    .bonus(BigDecimal.valueOf(2))
//                    .count(1)
//                    .statisticType(ActivateBonus.StatisticType.INDIRECTIVE)
//                    .time(new Date())
//                    .type(vipType)
//                    .build(), DateUtils.getCurrentMonthString());
//
//            balanceService.insertUserBalance(BalanceFlow.builder()
//                    .type(BalanceFlow.Type.ACTIVATE_BONUS)
//                    .amount(BigDecimal.valueOf(2))
//                    .description(new HashMap<String, String>(){{
//                        put("desc", vipType == User.VipType.PRIME ? "皇冠会员激活奖金发放" : "会员激活奖金发放");
//                    }})
//                    .userId(grandInvitor.getId())
//                    .occurTime(new Date())
//                    .additionalInfos("ACTIVATE.INVITEE." + vipType.name() + "." + activatingUser.getId())
//                    .build());
//
//
//            messageService.addMessage(Message.builder()
//                    .userId(grandInvitor.getId())
//                    .type(Message.Type.BONUS)
//                    .content("用户" + name + "给您贡献了一笔2元的激活奖金收益，如有疑问请{{联系客服}}。")
//                    .title("激活奖发放通知")
//                    .build(), true);
//        }
    }

    public void calculateMonthlyActivateBonus(String monthString, String rootUserId) {
        // 20200509 干掉间推奖
//        Map<String, Integer> resultCollections = new HashMap<>();
//        var c = DateUtils.calendarFromMonthString(monthString);
//        c.set(Calendar.DAY_OF_MONTH, 1);
//        DateUtils.setFirstTimeOfDay(c);
//        var startTime = c.getTime();
//
//        c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
//        DateUtils.setLastTimeOfDay(c);
//        var endTime = c.getTime();
//
//        recursiveCalcMonthlyActivateCount(rootUserId, null, null, resultCollections, startTime, endTime);
//
//        var entries = resultCollections.entrySet();
//        for (var en : entries) {
//            var bonus = calcActivateBonus(en.getValue());
//
//            activateBonusMapper.insert(ActivateBonus.builder()
//                    .amount(BigDecimal.ZERO)
//                    .type(User.VipType.PAYED)
//                    .time(new Date())
//                    .count(en.getValue())
//                    .bonus(bonus)
//                    .statisticType(ActivateBonus.StatisticType.INDIRECTIVE)
//                    .userId(en.getKey())
//                    .build(), monthString);
//
//
//            if (bonus.compareTo(BigDecimal.ZERO) > 0) {
//
//                balanceService.insertUserBalance(BalanceFlow.builder()
//                        .amount(bonus)
//                        .occurTime(new Date())
//                        .description(new HashMap<String, String>(){{
//                            put("desc", monthString + "激活奖发放");
//                        }})
//                        .userId(en.getKey())
//                        .type(BalanceFlow.Type.ACTIVATE_BONUS)
//                        .additionalInfos("ACTIVATE." + monthString)
//                        .build());
//
//                messageService.addMessage(Message.builder()
//                        .userId(en.getKey())
//                        .type(Message.Type.BONUS)
//                        .content(df.format(bonus) + "元激活奖已发放, 请去钱包查询")
//                        .title(monthString + "激活奖发放通知")
//                        .build(), true);
//            }
//        }


    }

    private BigDecimal calcActivateBonus(int count) {
        if (count >= 10) {
            return BigDecimal.valueOf(5 * count);
        }

        if (count == 9) {
            return BigDecimal.valueOf(27);
        }

        if (count >= 6) {
            return BigDecimal.valueOf(21);
        }

        if (count >= 3) {
            return BigDecimal.valueOf(4);
        }

        return BigDecimal.valueOf(0);

    }

    private void recursiveCalcMonthlyActivateCount(String me, String father, String grandFather, Map<String, Integer> result, Date startTime, Date endTime) {
        result.put(me, 0);

        if (grandFather != null) {
            int grandFatherCount = result.get(grandFather);
            grandFatherCount += userService.isVipConsumeOccurredInTimeRange(me, startTime, endTime);
            grandFatherCount += userService.isPrimeVipConsumeOccurredInTimeRange(me, startTime, endTime);

            result.put(grandFather, grandFatherCount);
        }

        var invitees = userService.getInviteeIds(me);
        for (var id : invitees) {
            recursiveCalcMonthlyActivateCount(id, me, father, result, startTime, endTime);
        }




    }



    // endregion








    @Data
    @Builder
    @AllArgsConstructor
    private static class ExecutionFlowForCal {
        private String id;
        private String userId;
        private BigDecimal amount;
        private Date createTime;
        private String vipType;

        public static ExecutionFlowForCal from(ExecutionFlow flow) {
            return new ExecutionFlowForCal(flow.getId(), flow.getUserId(), flow.getAmount(), flow.getCreateTime(), flow.getVipType());
        }

        public static ExecutionFlowForCal from(com.yhb.yinsheng.model.ExecutionFlow flow) {
            return new ExecutionFlowForCal(flow.getId(), flow.getUserId(), flow.getAmount(), flow.getCreateTime(), flow.getVipType());
        }

        public static ExecutionFlowForCal from(LeShuaFlow flow) {
            return new ExecutionFlowForCal(flow.getId(), flow.getUserId(), flow.getAmount(), flow.getOrderTime(), flow.getVipType());

        }
    }

    @Data
    @Builder
    private static class VipLevelCounter {
        @Builder.Default
        private int vip5Count = 0;

        /**
         * 仅返回和交易量的对应关系, v5数量需要计数
         *
         * @param amount
         * @return
         */
        public static Integer getRawVipLevel(BigDecimal amount, int vip5Count) {
            if (amount == null || amount.compareTo(BigDecimal.valueOf(50_0000L)) < 0) {
                return 1;
            }

            if (amount.compareTo(BigDecimal.valueOf(800_0000L)) < 0) {
                return 2;
            }

            if (amount.compareTo(BigDecimal.valueOf(1500_0000L)) < 0) {
                return 3;
            }

            if (amount.compareTo(BigDecimal.valueOf(3000_0000L)) < 0) {
                return 4;
            }

            if (amount.compareTo(BigDecimal.valueOf(5000_0000L)) < 0) {
                return 5;
            }

            if (amount.compareTo(BigDecimal.valueOf(1_0000_0000L)) < 0) {
                if (vip5Count < 1) return 5;
                return 6;
            }

            if (amount.compareTo(BigDecimal.valueOf(3_0000_0000L)) < 0) {
                if (vip5Count < 2) return 5;
                return 7;
            }

            if (amount.compareTo(BigDecimal.valueOf(7_0000_0000L)) < 0) {
                if (vip5Count < 3) return 5;
                return 8;
            }

            if (vip5Count < 5) return 5;

            return 9;
        }
    }

}
